Атрибуты, определяемые пользователем (UDA)
===========================================

Любому объявлению (например, типу структуры, типу класса, переменной и т. д.) можно присвоить
атрибуты, к которым можно обращаться во время компиляции, чтобы изменить способ компиляции кода
компиляции. Атрибуты, определяемые пользователем, являются исключительно функцией времени компиляции.
Синтаксис атрибутов, определяемых пользователем, состоит из знака @, за которым следует атрибут
атрибут появляется перед объявлением, которому он присваивается. Для
например, следующий код назначает атрибут Encrypted объявлению
name:

.. index::
    Encrypted

.. code-block:: d

    @Encrypted string name;

Несколько атрибутов могут быть указаны отдельно или в виде списка в круглых скобках
атрибутов. Например, обе следующие переменные имеют одинаковые атрибуты:

.. code-block:: d

    @Encrypted @Colored string lastName; // ← separately
    @(Encrypted, Colored) string address; // ← togethe

Атрибут может быть как именем типа, так и значением определенного пользователем или
фундаментального типа. Однако, поскольку их значения могут быть неясны, атрибуты
состоящие из буквальных значений типа 42, не рекомендуется использовать:

.. code-block:: d

    struct Encrypted
    {
    }

    enum Color
    {
        black,
        blue,
        red
    }

    struct Colored
    {
        Color color;
    }

    void main()
    {
        @Encrypted int a; // ← type name
        @Encrypted() int b; // ← object
        @Colored(Color.blue) int c; // ← object
        @(42) int d; // ← literal (discouraged)
    }

Приведенные выше атрибуты a и b относятся к разным типам: Атрибут a - это тип
Encrypted, а атрибут b - объект типа Encrypted. Это
важное различие, которое влияет на то, как атрибуты используются во время компиляции. Мы
пример этого различия мы увидим ниже.
Значение атрибутов определяется исключительно программистом для
для нужд программы. Атрибуты определяются с помощью
__traits(getAttributes) во время компиляции, и код компилируется в соответствии с
в соответствии с этими атрибутами.
Следующий код показывает, как атрибуты определенного члена struct (например.
Person.name) можно получить с помощью __traits(getAttributes):

.. code-block:: d

    import std.stdio;

    // ...
    struct Person
    {
        @Encrypted @Colored(Color.blue) string name;
        string lastName;
        @Colored(Color.red) string address;
    }

    void main()
    {
        foreach (attr; __traits(getAttributes, Person.name))
        {
            writeln(attr.stringof);
        }
    }

В выводе программы перечислены атрибуты Person.name:

.. code-block:: d

    Encrypted
    Colored(cast(Color)1)

Два других выражения __traits полезны при работе с определенными пользователем
атрибутами:
∙__traits(allMembers) выдает все члены типа (или модуля) в виде
строки.
∙__traits(getMember) выдает символ, полезный при обращении к члену.
Его первый аргумент - символ (например, тип или имя переменной), а второй
аргумент - строка. Она создает символ путем объединения первого аргумента, точки
точку, и второй аргумент. Например, __traits(getMember, Person,
" name") выдает символ Person.name

.. code-block:: d

    import std.string;

    // ...
    void main()
    {
        foreach (memberName; __traits(allMembers, Person))
        {
            writef("The attributes of %-8s:", memberName);
            foreach (attr; __traits(getAttributes,
                    __traits(getMember,
                    Person, memberName)))
            {
                writef(" %s", attr.stringof);
            }
            writeln();
        }
    }

В выводе программы перечислены все атрибуты всех членов Person:

.. code-block:: console

    The attributes of name    : Encrypted Colored(cast(Color)1)
    The attributes of lastName:
    The attributes of address : Colored(cast(Color)2)

Еще один полезный инструмент - std.traits.hasUDA, который определяет, имеет ли символ
имеет определенный атрибут. Следующий статический assert проходит, потому что
Person.name имеет атрибут Encrypted:

.. code-block:: d

    import std.traits;
    // ...
    static assert(hasUDA!(Person.name, Encrypted));

hasUDA может использоваться как с типом атрибута, так и с конкретным значением этого типа.
Следующие статические утверждения проверяют оба утверждения, поскольку Person.name имеет атрибут
атрибут Colored(Color.blue):

.. code-block:: d

    static assert(hasUDA!(Person.name, Colored));
    static assert(hasUDA!(Person.name, Colored(Color.blue)));

Пример
-------

Давайте разработаем шаблон функции, которая выводит значения всех членов объекта struct
в формате XML. Следующая функция учитывает атрибуты Encrypted и
Colored каждого члена при выводе:

.. code-block:: d

    void printAsXML(T)(T object)
    {
        // ...
        foreach (member; __traits(allMembers, T))
        { // (1)
            string value =
                __traits(getMember, object, member).to!string; // (2)
            static if (hasUDA!(__traits(getMember, T, member), // (3)
                    Encrypted))
            {
                value = value.encrypted.to!string;
            }
            writefln(`  <%1$s color="%2$s">%3$s</%1$s>`, member,
                colorAttributeOf!(T, member), value); // (4)
        }
    }

Ниже приведены пояснения к выделенным частям кода:
#. Члены типа определяются __traits(allMembers).
#. Значение каждого члена преобразуется в строку для последующего использования при
выводе на печать. Например, если членом является "name", то правое
выражение правой стороны становится object.name.to!string.
#. Каждый член проверяется с помощью hasUDA, чтобы определить, имеет ли он атрибут
атрибут Encrypted. Значение члена шифруется, если он имеет этот атрибут.
атрибут. (Поскольку для работы hasUDA требуются символы, обратите внимание на то, как
__traits(getMember) используется для получения члена в виде символа (например.
Person.name).)
#. Атрибут цвета каждого члена определяется с помощью функции colorAttributeOf(),
которую мы рассмотрим ниже.
Шаблон функции colorAttributeOf() может быть реализован как в
следующем коде:

.. code-block:: d

    Color colorAttributeOf(T, string memberName)()
    {
        foreach (attr; __traits(getAttributes,
                __traits(getMember, T, memberName)))
        {
            static if (is(typeof(attr) == Colored))
            {
                return attr.color;
            }
        }
        return Color.black;
    }

Когда оценки времени компиляции завершены, функция printAsXML()
шаблон будет инстанцирован для типа Person как эквивалент
следующей функции:

.. code-block:: d

    /* The equivalent of the printAsXML!Person instance. */
    void printAsXML_Person(Person object)
    {
        // ...
        {
            string value = object.name.to!string;
            value = value.encrypted.to!string;
            writefln(`  <%1$s color="%2$s">%3$s</%1$s>`,
                "name", Color.blue, value);
        }
        {
            string value = object.lastName.to!string;
            writefln(`  <%1$s color="%2$s">%3$s</%1$s>`,
                "lastName", Color.black, value);
        }
        {
            string value = object.address.to!string;
            writefln(`  <%1$s color="%2$s">%3$s</%1$s>`,
                "address", Color.red, value);
        }
    }

Полная версия программы содержит больше пояснений:

.. code-block:: d

    import std.stdio;
    import std.string;
    import std.algorithm;
    import std.conv;
    import std.traits;

    /* Specifies that the symbol that it is assigned to should be
    * encrypted. */
    struct Encrypted
    {
    }

    enum Color
    {
        black,
        blue,
        red
    }
    /* Specifies the color of the symbol that it is assigned to.
    * The default color is Color.black. */
    struct Colored
    {
        Color color;
    }

    struct Person
    {
        /* This member is specified to be encrypted and printed in
    * blue. */
        @Encrypted @Colored(Color.blue) string name;
        /* This member does not have any user defined
    * attributes. */
        string lastName;
        /* This member is specified to be printed in red. */
        @Colored(Color.red) string address;
    }
    /* Returns the value of the Colored attribute if the specified
    * member has that attribute, Color.black otherwise. */
    Color colorAttributeOf(T, string memberName)()
    {
        auto result = Color.black;
        foreach (attr; __traits(getAttributes,
                __traits(getMember, T, memberName)))
        {
            static if (is(typeof(attr) == Colored))
            {
                result = attr.color;
            }
        }
        return result;
    }
    /* Returns the Caesar-encrypted version of the specified
    * string. (Warning: Caesar cipher is a very weak encryption
    * method.) */
    auto encrypted(string value)
    {
        return value.map!(a => dchar(a + 1));
    }

    unittest
    {
        assert("abcdefghij".encrypted.equal("bcdefghijk"));
    }

    /* Prints the specified object in XML format according to the
    * attributes of its members. */
    void printAsXML(T)(T object)
    {
        writefln("<%s>", T.stringof);
        scope (exit)
            writefln("</%s>", T.stringof);
        foreach (member; __traits(allMembers, T))
        {
            string value =
                __traits(getMember, object, member).to!string;
            static if (hasUDA!(__traits(getMember, T, member),
                    Encrypted))
            {
                value = value.encrypted.to!string;
            }
            writefln(`  <%1$s color="%2$s">%3$s</%1$s>`,
                member, colorAttributeOf!(T, member), value);
        }
    }
    void main()
    {
        auto people = [
            Person("Alice", "Davignon", "Avignon"),
            Person("Ben", "de Bordeaux", "Bordeaux")
        ];
        foreach (person; people)
        {
            printAsXML(person);
        }
    }


Вывод программы показывает, что члены имеют правильный цвет и
что член с именем зашифрован:

.. code-block:: xml

    <Person>
        <name color="blue">Bmjdf</name> ← blue and encrypted
        <lastName color="black">Davignon</lastName>
        <address color="red">Avignon</address> ← red
    </Person>
    <Person>
      <name color="blue">Cfo</name> ← blue and encrypted
      <lastName color="black">de Bordeaux</lastName>
      <address   color="red">Bordeaux</address> ← red
    </Person>

Преимущество атрибутов, определяемых пользователем
---------------------------------------------------

Преимущество атрибутов, определяемых пользователем, заключается в возможности изменять атрибуты
деклараций без необходимости изменять какую-либо другую часть программы. Например,
например, все члены Person могут стать зашифрованными в выводе XML
с помощью тривиального изменения, приведенного ниже:

.. code-block:: d

    struct Person
    {
        @Encrypted
        {
            string name;
            string lastName;
            string address;
        }
    }
    // ...
    printAsXML(Person("Cindy", "de Cannes", "Cannes"));

Вывод

.. code-block:: xml

    <Person>
        <name color="black">Djoez</name> ← encrypted
        <lastName color="black">ef!Dbooft</lastName> ← encrypted
        <address color="black">Dbooft</address> ← encrypted
    </Person>

Кроме того, функцию printAsXML() и учитываемые ею атрибуты можно использовать и с другими
типами:

.. code-block:: d

    struct Data
    {
        @Colored(Color.blue) string message;
    }
    // ...
    printAsXML(Data("hello world"));

Вывод:

.. code-block:: xml

    <Data>
        <message color="blue">hello world</message> ← blue
    </Data>

Резюме
-------

* Атрибуты, определяемые пользователем, могут быть присвоены любому объявлению.
* Определенные пользователем атрибуты могут быть как именами типов, так и значениями.
* К определенным пользователем атрибутам можно обращаться во время компиляции с помощью функций hasUDA и
  __traits(getAttributes), чтобы изменить способ компиляции программы.
